﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.IO.Ports;
using System.Linq;
using System.Windows.Forms;
using Interface;
using LogicAnalyzerCommon;

namespace AvrLogicAnalyzer
{
    public class AvrAnalyzer : ILogicAnalyzer
    {
        private static readonly long[] _frequenciesSupported = new long[] { 1000, 2000, 5000, 10000, 20000, 50000, 100000, 200000, 500000, 1000000, 2000000, 5000000, 10000000 };
        private static readonly long[] _sampleSizesSupported = new long[] { 1024, 2048, 4096, 8192, 16384, 32768, 65536, (131072 - 256) };

        internal const int NumberOfChannels = 8;

        private SettingsControl _settingsControl;

        public IEnumerable<long> FrequenciesSupported { get { return _frequenciesSupported; } }
        public IEnumerable<long> SampleSizesSupported { get { return _sampleSizesSupported; } }
        public UserControl SettingsPanel { get { return _settingsControl; } }
        public string Name { get { return "AVR Logic Analyzer"; } }

        public string PrettyPrintSampleSettings(IDictionary<string, string> sampleSettings)
        {
            if (string.IsNullOrEmpty(GetPortName(sampleSettings)))
            {
                return string.Format("{0}, {1} samples, {2}", (_frequenciesSupported[GetSampleFrequencyIndex(sampleSettings)]).PrettyPrintFrequency(), _sampleSizesSupported[GetSampleSizeIndex(sampleSettings)], GetTriggerMode(sampleSettings).AsString());
            }
            else
            {
                return string.Format("{0}, {1}, {2} samples, {3}", GetPortName(sampleSettings), (_frequenciesSupported[GetSampleFrequencyIndex(sampleSettings)]).PrettyPrintFrequency(), _sampleSizesSupported[GetSampleSizeIndex(sampleSettings)], GetTriggerMode(sampleSettings).AsString());
            }
        }

        public IEnumerable<ICommunication> CommunicationDevices
        {
            get;
            private set;
        }

        public AvrAnalyzer(IDictionary<string, string> sampleSettings, IEnumerable<ICommunication> communicationDevices)
        {
            this.CommunicationDevices = communicationDevices;

            _settingsControl = new SettingsControl(this, sampleSettings);
        }

        public IData Load(Stream stream)
        {
            byte[] data = new byte[stream.Length];
            stream.Read(data, 0, (int)stream.Length);

            const int HeaderLength = 13;
            byte[] sampleData = new byte[data.Length - (HeaderLength + 1)];

            Array.Copy(data, HeaderLength/*skip header at start*/, sampleData, 0, data.Length - (HeaderLength + 1/* skip checksum at end */));

            IDictionary<string,string> acquireSampleSettings = CreateSampleSettingsFromSlaFile(data);

            // TODO check checksum
            //this.Checksum = data[data.Length - 1];

            return new SamplePacket(acquireSampleSettings, sampleData);
        }

        /// <summary>
        /// Save to simple logic analyzer format
        /// </summary>
        /// <param name="samplePacket"></param>
        /// <param name="stream"></param>
        public void Save(IData samplePacket, Stream stream)
        {
            // Byte 0: sample size in 256 blocks
            byte sampleSizeIn256ByteBlocks = 0;
            switch (GetSampleSizeIndex(samplePacket.SampleSettings))
            {
                case 0: sampleSizeIn256ByteBlocks = 4;   break; // 1024 bytes
                case 1: sampleSizeIn256ByteBlocks = 8;   break; // 2048 bytes
                case 2: sampleSizeIn256ByteBlocks = 16;  break; // 4096 bytes
                case 3: sampleSizeIn256ByteBlocks = 32;  break; // 8192 bytes
                case 4: sampleSizeIn256ByteBlocks = 64;  break; // 16384 bytes
                case 5: sampleSizeIn256ByteBlocks = 128; break; // 32768 bytes
                default: throw new InvalidSettingsException(SettingsControl.SampleSizeIndexKey);
            }
            stream.WriteByte(sampleSizeIn256ByteBlocks);
            // Byte 1: 0
            stream.WriteByte(0);
            // Byte 2: Sample frequency: 0 = 2 kHz, 1 = 20 kHz, 2 = 200 kHz, 3 = 2 MHz
            stream.WriteByte(GetSampleFrequencyIndex(samplePacket.SampleSettings));
            // Byte 3: Trigger mode: 0 = autocmp, 1 = autoedge, 2 = manual
            stream.WriteByte((byte)GetTriggerMode(samplePacket.SampleSettings));
            // Byte 4 - 11 (ch0..7): autocmp 0 = L, 1 = H; autoedge 0 = falling, 1 = rising
            byte autoCompareTriggerLevel = GetAutoCompareTriggerLevel(samplePacket.SampleSettings);
            for (byte channel = 0; channel < NumberOfChannels; channel++)
            {
                stream.WriteByte((byte)(autoCompareTriggerLevel & (1 << channel)));
            }
            // Byte 12: Autoedge channel: 0 = 0, ... , 7 = 7
            stream.WriteByte((byte)GetAutoEdgeChannelIndex(samplePacket.SampleSettings));

            // Write the data
            stream.Write(samplePacket.SampleData.ToArray(), 0, samplePacket.SampleData.Count());

            // Write the check sum
            stream.WriteByte(samplePacket.SampleData.Crc8Dallas());
        }

        private string GetPortName(IDictionary<string, string> sampleSettings)
        {
            string key = Key(SettingsControl.PortNameKey);
            if (!sampleSettings.ContainsKey(key) ||
                string.IsNullOrEmpty(sampleSettings[key]))
            {
                return string.Empty;
            }
            return sampleSettings[key];
        }

        public void Acquire(BackgroundWorker backgroundWorker, DoWorkEventArgs doWorkEventArgs)
        {
            IDictionary<string, string> acquireSampleSettings = new Dictionary<string, string>();

            byte[] commsBuffer = new byte[10000];

            IAcquireArgument argument = doWorkEventArgs.Argument as IAcquireArgument;
            IDictionary<string, string> sampleSettings = argument.SampleSettings;

            foreach (string key in argument.SampleSettings.Keys.Where(k => k.StartsWith(SettingsControl.AvrLogicAnalyzer)))
            {
                acquireSampleSettings[key] = argument.SampleSettings[key];
            }

            if (string.IsNullOrEmpty(GetPortName(sampleSettings)))
            {
                throw new InvalidSettingsException("No port defined");
            }

            // instruct analyser to gather data, wait for it to complete & retrieve data.
            // Instantiate port to analyser
            using (SerialPort serialPort = new SerialPort(GetPortName(sampleSettings), 115200, Parity.None, 8, StopBits.One))
            {
                MessageReconstruct messageReconstruct = new MessageReconstruct(backgroundWorker, serialPort, this, acquireSampleSettings);

                serialPort.ReadTimeout = 100;
                // Open the port for communications
                serialPort.Open();

                messageReconstruct.StartCommunication(); // startpoint of comms sequence in ComssFMS

                while (!messageReconstruct.SamplingDone())
                {
                    if (serialPort.BytesToRead > 0)
                    {
                        int bytesRead = serialPort.Read(commsBuffer, 0, commsBuffer.Length);
                        messageReconstruct.ProcessBytes(commsBuffer, bytesRead);
                    }
                    messageReconstruct.CommunicationsEngine();
                }

                // Communicate the result to the GUI
                doWorkEventArgs.Result = new SamplePacket(acquireSampleSettings, messageReconstruct.Data);

                serialPort.Close();
                Console.WriteLine("communications done");
            }
        }

        private IDictionary<string,string> CreateSampleSettingsFromSlaFile(IList<byte> data)
        {
            IDictionary<string,string> sampleSettings = new Dictionary<string,string>();

            // Byte 0: sample size index
            byte sampleSizeIndex = 0;
            switch (data[0])
            {
                case 4:   sampleSizeIndex = 0; break;
                case 8:   sampleSizeIndex = 1; break;
                case 16:  sampleSizeIndex = 2; break;
                case 32:  sampleSizeIndex = 3; break;
                case 64:  sampleSizeIndex = 4; break;
                case 128: sampleSizeIndex = 5; break;
                default: throw new InvalidSettingsException("Sample Size (byte 0)");
            }
            sampleSettings.Add(Key(SettingsControl.SampleSizeIndexKey), sampleSizeIndex.ToString());

            // Byte 1: should be 0

            // Byte 2: Frequency index
            sampleSettings.Add(Key(SettingsControl.SampleFrequencyIndexKey), data[2].ToString());

            // Byte 3: trigger mode
            TriggerMode triggerMode = TriggerMode.Unknown;
            switch (data[3])
            {
                case 0:
                    triggerMode = TriggerMode.AutoCmp;
                    break;
                case 1:
                    triggerMode = TriggerMode.AutoEdge;
                    break;
                case 2:
                    triggerMode = TriggerMode.Manual;
                    break;
                default:
                    throw new InvalidSettingsException(string.Format("Unknown Trigger Mode (byte 3): '{0}'. It should be 0, 1 or 2.", data[3]));
            }
            sampleSettings.Add(Key(SettingsControl.TriggerModeKey), Enum.GetName(typeof(TriggerMode), triggerMode));

            // Byte 4..11: trigger level per channel
            int filter = 1;
            byte autoCompareTriggerLevel = 0;
            for (int channel = 0; channel < AvrAnalyzer.NumberOfChannels; channel++)
            {
                if (data[channel + 4] == 1)
                {
                    autoCompareTriggerLevel |= (byte)(filter <<= channel);
                }
            }
            sampleSettings.Add(Key(SettingsControl.AutoCompareTriggerLevelKey), autoCompareTriggerLevel.ToString());

            // Byte 12: auto edge channel (0..7)
            if (data[12] > 8)
            {
                throw new InvalidSettingsException(string.Format("Invalid Auto Edge Channel Index: '{0}'. It should be between 0 and 7.", data[12]));
            }
            sampleSettings.Add(Key(SettingsControl.AutoEdgeChannelIndexKey), data[12].ToString());

            return sampleSettings;
        }

        //private ICommunication GetCommunicationDevice(string portName)
        //{
        //    foreach (ICommunication communicationDevice in this.CommunicationDevices)
        //    {
        //        if (communicationDevice.PortNames.Contains(portName))
        //        {
        //            if (portName.StartsWith("COM"))
        //            {
        //                communicationDevice.PortName = portName;
        //            }

        //            return communicationDevice;
        //        }
        //    }
        //    return null;
        //}

        public long GetSampleFrequency(IDictionary<string, string> sampleSettings)
        {
            string value;
            string key = Key(SettingsControl.SampleFrequencyIndexKey);
            sampleSettings.CreateDefaultValueForMissingKey(key, "0");
            if (sampleSettings.TryGetValue(key, out value))
            {
                return _frequenciesSupported[long.Parse(value)];
            }
            return 0L;
        }

        internal static long GetSampleSize(IDictionary<string, string> sampleSettings)
        {
            string value;
            string key = Key(SettingsControl.SampleSizeIndexKey);
            sampleSettings.CreateDefaultValueForMissingKey(key, "0");
            if (sampleSettings.TryGetValue(key, out value))
            {
                return _sampleSizesSupported[long.Parse(value)];
            }
            return 0L;
        }

        internal static TriggerMode GetTriggerMode(IDictionary<string, string> sampleSettings)
        {
            string key = Key(SettingsControl.TriggerModeKey);
            sampleSettings.CreateDefaultValueForMissingKey(key, Enum.GetName(typeof(TriggerMode), TriggerMode.Manual));
            return (TriggerMode)Enum.Parse(typeof(TriggerMode), sampleSettings[key]);
        }

        internal static byte GetAutoCompareTriggerLevel(IDictionary<string, string> sampleSettings)
        {
            string key = Key(SettingsControl.AutoCompareTriggerLevelKey);
            sampleSettings.CreateDefaultValueForMissingKey(key, "0");
            return byte.Parse(sampleSettings[key]);
        }

        internal static byte GetAutoEdgeChannelIndex(IDictionary<string, string> sampleSettings)
        {
            string key = Key(SettingsControl.AutoEdgeChannelIndexKey);
            sampleSettings.CreateDefaultValueForMissingKey(key, "0");
            return byte.Parse(sampleSettings[key]);
        }

        internal static TriggerEdge GetAutoEdgeDirection(IDictionary<string, string> sampleSettings)
        {
            string key = Key(SettingsControl.AutoEdgeDirectionKey);
            sampleSettings.CreateDefaultValueForMissingKey(key, Enum.GetName(typeof(TriggerEdge), TriggerEdge.Rising));
            return (TriggerEdge)Enum.Parse(typeof(TriggerEdge), sampleSettings[key]);
        }

        internal static byte GetPreTriggerMemoryPercentage(IDictionary<string, string> sampleSettings)
        {
            string key = Key(SettingsControl.PreTriggerMemoryPercentageKey);
            sampleSettings.CreateDefaultValueForMissingKey(key, "0");
            return byte.Parse(sampleSettings[key]);
        }

        private static byte GetSampleFrequencyIndex(IDictionary<string, string> sampleSettings)
        {
            string key = Key(SettingsControl.SampleFrequencyIndexKey);
            sampleSettings.CreateDefaultValueForMissingKey(key, "0");
            return byte.Parse(sampleSettings[key]);
        }

        private static byte GetSampleSizeIndex(IDictionary<string, string> sampleSettings)
        {
            string key = Key(SettingsControl.SampleSizeIndexKey);
            sampleSettings.CreateDefaultValueForMissingKey(key, "0");
            return byte.Parse(sampleSettings[key]);
        }

        internal static string Key(string key)
        {
            return string.Format("{0}.{1}", SettingsControl.AvrLogicAnalyzer, key);
        }
    }
}
